#include "common.h" 
#include "db.h"


int new_ret_val;
int old_ret_val;
char *db_conf[5];

void use_age(void)
{
 char *display_help = "Useage: monitor_mysql -L label -[i|U] [OPTION...] -e [OPTION...]\n \
     Examples: \n \
     \n \
     Display QPS \n \
       monitor_mysql -L label -i qps \n \
       monitor_mysql -L db-1 -i inno_stat -U \n \
     Display buffer_pool_size \n \
       monitor_mysql -L db-1 -i inno_stat -e buffer_pool_size \n \
     \n \
     Update MySQL Status \n \
       monitor_mysql -L label -U \n \
       \n \
     Mandatory arguments \n \
     \n \
     -i \n\
        to monitor item name。\n \
      -e \n\
        extend item for inno_stat \n \
     -L \n\
        the label of configure file。 \n \
     -U \n\
        update mysql old status。 \n\
      \n \
     [OPTION] \n \
     qps \t\t Query Per Second\n \
     net_rece \t\t Bytes_received \n \
     net_sent \t\t Bytes_sent \n \
     delay_time \t The Time Slave Behind \n \
     tps \t\t Transaction Per Second \n \
     slow_log \t\t Show Slow Log Frequency \n \
     inno_read_hit \t InnoDB Buffer Read Hit \n \
     threads_connected\t Threads_connected \n \
     threads_running \t Threads_running \n \
     qcache_hit \t Query Cache hit \n \
     tmp_disk_tables \t Created_tmp_disk_tables \n \
     inno_stat \t get status from show engine InnoDB status,must with -e option \n \
     \n \
     \n \
     Configure File Example: \n \
     \n \
     [db-1] \n \
     host = 192.168.10.153 \n \
     dbname = test \n \
     dbuser = zabbix  \n \
     dbpasswd = zabbix \n \
     dbport = 3306 \n \
     [db-2] \n \
     host = 192.168.10.153 \n \
     dbname = test \n \
     dbuser = zabbix \n \
     dbpasswd = zabbix \n \
     dbport = 3307 \n \
     \n \
     The label value is db-1 and db-2 in the configure file. \n \
     \n \
Email: zoufuxing@gmail.com \n \
ShopEx MySQL Monitor Plugin For Zabbix,Write By zoufuxing.\n";

   printf("%s\n",display_help);
   exit(-1);
}

/* 查询老记录 */
int get_old_status(char *variable_name)
{
  /* 读取一行 */
  char buf[1024];
  /* key 和 value 分别存放变量名以及变量对应的状态值 */
  char key[1024];
  char *value = NULL;
  /* 文件句柄 */
  FILE *fd;
  /* len读取的一行的长度 */
  int len = 0;
  int i = 0;
  int j = 0;
  int k = 0;

  char *status_file = NULL;
  char *filename = "/tmp/.status_%s";
  /* 初始化指针 */
  status_file = (char *)malloc(64);
  value = (char *)malloc(1024);

  sprintf(status_file,filename,label);

  /* 打开文件句柄 */
  if( (fd = fopen(status_file,"r")) == NULL )
  {
    fprintf(stderr,"Can't Open Status File!\n");
    exit(-1);
  }

  /* 每次读一行 */
  while((fgets(buf,1024,fd)) != NULL)
  {
   len = strlen(buf);

   /* 读取key，即将空格或者\t左边读取到作为key */   
   for(i = 0;i < len -1;i++)
   {
     /* 遇到空格或者\t，跳出循环，剩下的由另外一个循环处理 */
     if((buf[i] == ' ') || (buf[i] == '\t'))
       break;
    
     key[i] = buf[i];
   }   
  
   /* 设置读取结束符，当printf遇到这个符号，就不再读取下去 */
   key[i] = '\0';

    /* 如果key不是想要找的，读取下一行，没必要再读value */
   if(strcmp(key,variable_name) != 0)
     continue;

   /* 从上次读取的断点开始读 */
   for(j = i;j < len - 1;j++)
   {
       if(buf[j] == ' ')
         continue;
       /* 读到换行符，说明一行已经读取结束 */
       if(buf[j] == '\n')
         break;
       *(value + k) = buf[j];
       k++;
   }
   
   *(value + k) = '\0';

 }

  return atoi(value);
}

/* 获取主从同步延时时间，需要给相应的用户Replication client 或者super的权限 */
int get_slave_behind_master(void)
{
  MYSQL *mysql;
  MYSQL_RES *res;
  MYSQL_ROW row;
  char *ret_val;
  
  /* 从配置文件获取相应设置 */
  char **cfg_ret = GetProfileValue(label);


  char *DB_HOST = cfg_ret[0];
  char *DB_USER = cfg_ret[1];
  char *DB_PASSWD = cfg_ret[2];
  char *DB_NAME = cfg_ret[3];
  unsigned int DB_PORT =  atoi(cfg_ret[4]);

  mysql = mysql_init(NULL);

  if(!mysql_real_connect(mysql,DB_HOST,DB_USER,DB_PASSWD,DB_NAME,DB_PORT,NULL,0))
  {
    fprintf(stderr,"%s\n",mysql_error(mysql));
    exit(-1);
  }

  char *sql = "show slave status";
  
  if(!mysql_query(mysql,sql))
  {
    res = mysql_store_result(mysql);
  }else
  {
    fprintf(stderr,"%s\n",mysql_error(mysql));
    exit(-1);
  }

  
  while((row = mysql_fetch_row(res)))
  {
    if(row[32] == NULL)
    {
      return -1;
    }else
    {  
      new_ret_val = atoi(row[32]);
    }
  }
  
  return  new_ret_val;
}


/* new status */
int get_new_status(char *variable_name)
{
  MYSQL *mysql;
  MYSQL_RES *res;
  MYSQL_ROW  row;
  char sql[512];
  char *ret_val = NULL;
  int ret;

  /* 从配置文件获取相应设置 */
  char **cfg_ret = GetProfileValue(label);
  char *DB_HOST = cfg_ret[0];
  char *DB_USER = cfg_ret[1];
  char *DB_PASSWD = cfg_ret[2];
  char *DB_NAME = cfg_ret[3];
  unsigned int DB_PORT =  atoi(cfg_ret[4]);

  mysql = mysql_init(NULL);

  if(!mysql_real_connect(mysql,DB_HOST,DB_USER,DB_PASSWD,DB_NAME,DB_PORT,NULL,0))
  {
    fprintf(stderr,"Can't connect MySQL!\n",mysql_error(mysql));
    exit(-1);
  }

  char *status_sql = "show global status";

  if(!mysql_query(mysql,status_sql))
  {
     res = mysql_store_result(mysql);
  }else
  {
    fprintf(stderr,"Can't query %s\n",mysql_error(mysql));
    exit(-1);
  }

  while(row = mysql_fetch_row(res) )
  {
    if(strcmp(variable_name,row[0]) == 0 )
    {
       ret_val = row[1];
       /* 得到需要的值，退出循环 */
       break;
    }
  }

  new_ret_val = atoi(ret_val);

  mysql_free_result(res);
  mysql_close(mysql);
  return new_ret_val;
}


/* QPS */
float get_mysql_qps(void)
{
  int old_status = get_old_status("Queries");
  int old_time = get_old_status("Uptime");

  int new_status = get_new_status("Queries");
  int new_time = get_new_status("Uptime");

  if( new_time == old_time)
    return (float)0;

  return ((new_status - old_status) * 1.0 /(new_time - old_time)) ;

}


/* TPS */
float get_mysql_tps(void)
{
  int commit_old_status = get_old_status("Com_commit");
  int rollback_old_status = get_old_status("Com_rollback");
  int old_time  = get_old_status("Uptime");

  int commit_new_status = get_new_status("Com_commit"); 
  int rollback_new_status = get_new_status("Com_rollback");
  int new_time = get_new_status("Uptime");

  int commit_increase = (commit_new_status - commit_old_status);

  int rollback_increase = (rollback_new_status - rollback_old_status);

  int increase = (commit_increase + rollback_increase);
  /*
  printf("Increase %d\n",increase);
  printf("time %d\n",(new_time - old_time));
  */
  if(new_time == old_time)
    return (float)0;

  float tps = (float)(increase * 1.0 / (new_time - old_time) );

  //printf("%0.2f\n",tps);

  return tps;
}

/* Slow Log*/
float get_mysql_slow_log_frequency(void)
{
  int old_status = get_old_status("Slow_queries");
  int old_time = get_old_status("Uptime");

  int new_status = get_new_status("Slow_queries");
  int new_time = get_new_status("Uptime");

  if(old_time == new_time)
    return 0;

  return (new_status - old_status) * 1.0 / (new_time - old_time);
}

/* 网络 */
float mysql_network_per_sec(char *item)
{
  int old_status = get_old_status(item);
  int old_time = get_old_status("Uptime");
  int new_status = get_new_status(item);
  int new_time = get_new_status("Uptime");

  if(new_time == old_time)
    return (float)0;

  return ((new_status - old_status) / (new_time - old_time));

}

float get_inno_buffer_read_hit(void)
{
  int pool_read = get_new_status("Innodb_buffer_pool_reads");
  int pool_read_requests = get_new_status("Innodb_buffer_pool_read_requests");

  return (1 - (pool_read / pool_read_requests) * 1.0) * 100.0;
}

int get_file_size(char *filename)
{
  int fd = open(filename,O_RDONLY);
  off_t size;
  if(fd == -1)
  {
    fprintf(stderr,"Can't open file !\n");
    exit(-1);
  }

  size=lseek(fd,0L,SEEK_END);
  return (int)size;
}


/* Threads_connected */
int get_threads_connected(void)
{
  return get_new_status("Threads_connected");
}

/* Threads_running */
int get_threads_running(void)
{
  return get_new_status("Threads_running");
}

/* Query Cache hit */
float get_query_cache_hit(void)
{
  int qcache_hits = get_new_status("Qcache_hits");
  int qcache_inserts = get_new_status("Qcache_inserts");
  if( qcache_hits == 0 )
    return 0.00;

  return ( qcache_hits / (qcache_hits + qcache_inserts ) ) * 100.0; 
}

/* Created_tmp_disk_tables */
float get_disk_table_per_sec(void)
{
  int old_status = get_old_status("Created_tmp_disk_tables");
  int old_time = get_old_status("Uptime");

  int new_status = get_new_status("Created_tmp_disk_tables");
  int new_time = get_new_status("Uptime");

  if(new_time == old_time)
    return 0;

  return (new_status - old_status) * 1.0 / (new_time - old_time);

}


/* Update mysql status */
void update_mysql_status()
{
  MYSQL *mysql;
  MYSQL_RES *res;
  MYSQL_ROW row;
  FILE *fd;
  char sql[128];
  char *query_sql = "show global status";

  char *status_file = NULL;
  char *filename = "/tmp/.status_%s";

  status_file = (char *)malloc(64);

//printf("%s\n",label);
  sprintf(status_file,filename,label);
//printf("%s\n",status_file);

  /* 打开文件句柄 */
  if( (fd = fopen(status_file,"w+")) == NULL )
  {
    fprintf(stderr,"Open Status File Failure!\n");
    exit(-1);
  }

  /* 从配置文件获取相应设置 */
  char **cfg_ret = GetProfileValue(label);
  char *DB_HOST = cfg_ret[0];
  char *DB_USER = cfg_ret[1];
  char *DB_PASSWD = cfg_ret[2];
  char *DB_NAME = cfg_ret[3];
  unsigned int DB_PORT =  atoi(cfg_ret[4]);


  mysql =  mysql_init(NULL);

  if(!mysql_real_connect(mysql,DB_HOST,DB_USER,DB_PASSWD,DB_NAME,DB_PORT,NULL,0))
  {
    fprintf(stderr,"Connect DB failure %s\n",mysql_error(mysql));
    exit(-1);
  }

  if(!mysql_query(mysql,query_sql))
  {
    res = mysql_store_result(mysql);
  }else
  {
    fprintf(stderr,"Can't Query %s\n",mysql_error(mysql));
  }


  while(row = mysql_fetch_row(res))
  {
    //printf("%s --> %s\n",row[0],row[1]);
    fwrite(row[0],strlen(row[0]),1,fd);
    fwrite(" ",strlen(" "),1,fd);
    fwrite(row[1],strlen(row[1]),1,fd);
    fwrite("\n",strlen("\n"),1,fd);
  }

  mysql_free_result(res);
  mysql_close(mysql);
  fclose(fd);
  //exit(0);
}


/* 获取 show engine innodb status 状态信息，将状态信息写到指定文本 */
int innodb_status_value(void)
{
  MYSQL *mysql;
  MYSQL_RES *res;
  MYSQL_ROW row;
  /*  文件句柄 */
  FILE *fd;

  pcre *re;
  const char *error;
  int erroffset;
  int ovector[OVECCOUNT];
  int rc,i;
  char matched[BUFLEN];
  char pattern[BUFLEN];

  char *src = NULL;
  src = (char *)malloc(1024);

  int j = 0;
  int length = 0;
  int len = 0;
  
  /*  每个状态文件，都会以配置文件里的[lable]结尾 */
  char *file_path = "/tmp/.innodb_status_%s";
  char *file_name = NULL;

  file_name = (char *)malloc(128);

  sprintf(file_name,file_path,label);
  //printf("label = %s\n",label);
  // printf("%s\n",status);
  printf("file name = %s\n",file_name);


  /* 从配置文件获取数据库相应设置 */
  char **cfg_ret = GetProfileValue(label);
  char *DB_HOST = cfg_ret[0];
  char *DB_USER = cfg_ret[1];
  char *DB_PASSWD = cfg_ret[2];
  char *DB_NAME = cfg_ret[3];
  unsigned int DB_PORT =  atoi(cfg_ret[4]);

  /* 打开要写入的文件句柄,内容是innodb状态信息 */
  if( (fd = fopen(file_name,"w+")) == NULL)
  {
    fprintf(stderr,"Can't open file!\n");
    /* 打开失败，退出程序 */
    exit(1);
  }

  mysql = mysql_init(NULL);

  if(!mysql_real_connect(mysql,DB_HOST,DB_USER,DB_PASSWD,DB_NAME,DB_PORT,NULL,0))
  {
    fprintf(stderr,"%s\n",mysql_error(mysql));
  }

  /* 查询语句 */
  char *sql = "show engine innodb status";
  
  if(!mysql_query(mysql,sql))
  {
    res = mysql_store_result(mysql);
  }else
  {
    fprintf(stderr,"%s\n",mysql_error(mysql));
    exit(-1);
  }

  while(row = mysql_fetch_row(res))
  {
    printf("row[2] = %s\n",row[2]);
    src = row[2]; 
  }
 

  char *row_op_pattern[] = {"\\sibuf\\saio\\sreads:\\s(\\d+)",  //ibuf aio reads  FILE I/O
                            ",\\slog\\si/o's:\\s(\\d+)", // log i/o's
                            ",\\ssync\\si/o's:\\s(\\d+)",// sync i/o's
                            "\n(.*)OS\\sfile\\sreads", // OS file reads
                            ",\\s(\\d+)\\sOS\\sfile\\swrites", // OS file writes
                            ",\\s(\\d+)\\sOS\\sfsyncs", //OS fsyncs 
                            "(\\d+\\.\\d+)\\sreads/s", //FILE I/O  reads/s
                            ",\\s(\\d+)\\savg\\sbytes/read",//FILE I/O avg bytes/read
                            ",\\s(\\d+\\.\\d+)\\swrites/s", //FILE I/O  writes/s
                            ",\\s(\\d+\\.\\d+)\\sfsyncs/s", //FILE I/O fsyncs/s
                            "\\sinsert\\s(\\d+)",//INSERT BUFFER AND ADAPTIVE HASH INDEX.insert num of merged operations
                            ",\\sdelete\\smark\\s(\\d+)", // Delete mark
                            ",\\sdelete\\s(\\d+)", //delete 
                            "discarded\\soperations:\n\\sinsert\\s(\\d+)",//discarded operations: inserts/s
                            "discarded\\soperations:\n\\sinsert\\s(\\d+),\\sdelete\\smark\\s(\\d+)", //delete mark
                            "discarded\\soperations:\n\\sinsert\\s(\\d+),\\sdelete\\smark\\s(\\d+),\\sdelete\\s(\\d+)",//deleter
                            "(\\d+\\.\\d+)\\shash\\ssearches/s", //hash searches/s
                            ",\\s(\\d+\\.\\d+)\\snon-hash\\ssearches/s", //non-hash searches/s
                            ",\\s(\\d+\\.\\d+)\\slog\\si/o's/second",//LOG log i/o's/second
                            "Total\\smemory\\sallocated\\s(\\d+)",//BUFFER POOL AND MEMORY.Total memory allocated
                            "Dictionary\\smemory\\sallocated\\s(\\d+)", //Dictionary memory allocated
                            "Buffer\\spool\\ssize\\s+(\\d+)",//Buffer pool size   
                            "Free\\sbuffers\\s+(\\d+)",//Free buffers
                            "Database\\spages\\s+(\\d+)",//Database pages
                            "Old\\sdatabase\\spages\\s(\\d+)",//Old database pages
                            "Modified\\sdb\\spages\\s+(\\d+)",// Modified db pages
                            "\n(.*)\\syoungs/s", //youngs/s
                            ",\\s(\\d+\\.\\d+)\\snon-youngs/s", //non-youngs/s
                            "\n(.*)\\sreads/s", //Pages reads/s
                            ",\\s(\\d+\\.\\d+)\\screates/s", //Pages creates/s
                            ",\\s(\\d+\\.\\d+)\\swrites/s",//Pages writes/s
                            "Buffer\\spool\\shit\\srate\\s(\\d+\\s/\\s\\d+)", // Buffer pool hit rate
                            "Pages\\sread\\sahead\\s(\\d+\\.\\d+)/s", //Pages read ahead 0.00/s
                            ",\\sevicted\\swithout\\saccess\\s(\\d+\\.\\d+)/s", // evicted without access 0.00/s
                            ",\\sRandom\\sread\\sahead\\s(\\d+\\.\\d+)/s", //Random read ahead 0.00/s
                            "\n(.*)queries\\sinside\\sInnoDB,", //queries inside InnoDB  ROW OPERATIONS
                            ",\\s(\\d+)\\squeries\\sin\\squeue", //queries in queue
                            "Number\\sof\\srows\\sinserted\\s(\\d+)", //Number of rows inserted
                            ",\\supdated\\s(\\d+),", //Number of rows updated
                            ",\\sdeleted\\s(\\d+),", // Number of rows deleted
                            ",\\sread\\s(\\d+)",   //Number of rows read
                            "(\\d+\\.\\d+)\\sinserts/s",  // Insert row per second
                            ",\\s(\\d+\\.\\d+)\\supdates/s,",  //Update row per second
                            ",\\s(\\d+\\.\\d+)\\sdeletes/s,",  //Delete row per second
                            ",\\s(\\d+\\.\\d+)\\sreads/s"  //Read row per second
  };
  
  /* 获取指针数组元素的个数,即有多少个匹配选项 */
  length = sizeof(row_op_pattern) / sizeof(char *);

  //printf("length = %d\n",length);

   for(j = 0;j < length;j++)
  {
    /* 编译正则 */
    re = pcre_compile(row_op_pattern[j],0,&error,&erroffset,NULL);
    if(re  == NULL)
    {
      /* 有错误不退出，继续下个匹配 */
      printf("PCRE compilation failed at offset %d: %s\n", erroffset, error);
      //return 1;
    }
    /*  执行模式匹配 */
    rc  = pcre_exec(re,NULL,src,strlen(src),0,0,ovector,OVECCOUNT);

    if(rc < 0)
    {
      /* 匹配不成功，报错，不退出，继续下一个匹配 */
      if (rc == PCRE_ERROR_NOMATCH) printf("Sorry, no match ...\n");
      else    printf("Matching error %d\n", rc);
      //free(re);
      //return 1;
    }
  
    //printf("rc = %d\n",rc);
     
    for (i = 1; i < rc; i++) {
      char *substring_start = src + ovector[2*i];
      int substring_length = ovector[2*i+1] - ovector[2*i];
      len  = substring_length;
      //printf("%2d: %.*s\n", i, substring_length, substring_start);
      //printf("%d\n",substring_length);
      //printf("%.*s\n",substring_length,substring_start);
      memcpy(matched,substring_start,substring_length);
    }
    matched[len] = '\0';
    //printf("matched = %s\n",matched);
    //printf("%d\n",length);
    fwrite(matched,strlen(matched),1,fd);
    fwrite("\n",strlen("\n"),1,fd);
    }

   fclose(fd);
  free(re);
  mysql_close(mysql);
  return 0;
}


/* 获取状态值 */
float status_value(char *status)
{
  FILE *fd;
  char buf[16];
  char ret[16];

  int len = 0;
  int i = 0;
  int j = 0;
  int arr_index = 0;
  char *file_path = "/tmp/.innodb_status_%s";
  char *file_name = NULL;

  file_name = (char *)malloc(128);

  sprintf(file_name,file_path,label);
  //printf("label = %s\n",label);
  // printf("%s\n",status);
  printf("file name = %s\n",file_name);

  if( (fd = fopen(file_name,"r")) == NULL )
  {
    fprintf(stderr,"Can't open file!\n");
    return 1;
  }
                           

  char *int_status_key[] = {"ibuf_aio_reads",
                    "log_ios",
                    "sync_ios",
                    "io_reads",
                    "io_avg_bytes_read",
                    "io_writes",
                    "io_fsyncs",
                    "os_file_reads",
                    "os_file_writes",
                    "os_fsyncs",
                    "insert_num_of_merged_operations", /* INSERT BUFFER AND ADAPTIVE HASH INDEX */
                    "discard_op_insert",
                    "delete_mark",
                    "delete",
                    "discarded_op_delete_mark",
                    "discarded_op_delete",
                    "hash_searches",
                    "no_hash_searches", 
                    "log_io_per_second",                            
                    "total_memory_allocated",
                    "dictionary_memory_allocated",
                    "buffer_pool_size",
                    "free_buffer",
                    "database_pages",
                    "old_database_pages",
                    "modified_db_pages",
                    "youngs_per_second",
                    "non_youngs_per_second",
                    "page_read_per_second",
                    "page_create_per_second",
                    "page_write_per_second",
                    "buffer_pool_hit_reate",
                    "page_read_ahead_per_second",
                    "evicted_without_access",
                    "random_read_ahead_per_second",
                    "query_inside_innodb_row_op",
                    "query_in_queue",
                    "number_rows_inserted",
                    "number_rows_updated",
                    "number_rows_delete",
                    "number_rows_read",
                    "insert_row_per_second",
                    "update_row_per_second",
                    "delete_row_per_second",
                    "read_row_per_second"
  };

  /* 获取指针数组元素的个数 */
  int arr_len = sizeof(int_status_key) /sizeof(char *);
  
  for(i = 0;i < arr_len;i++)
  {
    if(strcmp(status,int_status_key[i]) == 0)
    {
      /* 获取索引号，即数组下标 */
      printf("arr_index = %d\n",arr_index);
      arr_index = i;
      /* 找到相应的元素，立刻跳出for循环 */
      break;
    }else
      arr_index = -1;
  }
 
  /* 没有匹配项，报错退出 */
  if(arr_index == -1)
  {
    fprintf(stderr,"Can't find Element %s!\n",status);
    exit(1);
  }
  const int real_index =  arr_index;
  
  //printf("int_status_key[%d] = %s\n",arr_index,int_status_key[arr_index]);
  
  while( fgets(buf,16,fd) != NULL) //每次读一行
  {
    len = strlen(buf);
    
    j++; //每读一行，自增1，与索引号比较

    /* 如果没有获取指定的索引号，直接跳过，不读取该行.
     * 另外，如果想要获取指针数组int_status_key第5个元素对应的值，那么，在文件里，应该跳过4行，
     * 第5行才是需要获取的值。而数组是从0开始算的，第5个元素对应的real_index的值是5，在if判断
     * 条件里，j从1开始数，所以比较的时候，j需要减1。这样才能保证读取到正确的值，如果使用j <
     * real_index作为条件的话，则只会读取到需要数值的前一个。
     */
    if( (j - 1) < real_index)
      continue;
    
    //printf("j = %d\n",j);
    for(i = 0; i < len -1 ;i++)
    {
      /* 读完一行，跳出*/
      if(buf[i] == '\n')
        break;
      //printf("%c",buf[i]);
      ret[i] = buf[i];
    }
    //printf("\n");
    ret[i] = '\0';
    break;
  }  
  // printf("ret = %s\n",ret);

  fclose(fd);
  return atof(ret);
}
